package edu.pitt.cs.cs2510.project3.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;

import edu.pitt.cs.cs2510.project3.configuration.InputParameters;

public class DynamicFrontEnd extends FrontEnd {

	private int workerId = 0;
	public Map<Integer, WorkerServer> deadWorkers;
	
	public DynamicFrontEnd(int id, InputParameters simulationParameters, Object monitor) {
		super(id, simulationParameters, monitor);
		int workerIndex = 0;
		
		deadWorkers = new HashMap<Integer, WorkerServer>();
		int filesPerWorker = (int) Math.floor((double)simulationParameters.getStorageSize() / (double)simulationParameters.getBlocksPerFile());
		int numWorkers = (int) Math.ceil((double)simulationParameters.getFileCount() / (double)filesPerWorker);
		
		System.out.println("Number of workers: " + numWorkers);
		for (workerId = 0; workerId < numWorkers; workerId++){
			this.workers.add(new WorkerServer(workerId, simulationParameters, this, monitor));
		}
		File newFile;
		for (File f : files.values()) {
			newFile = f.clone();
			newFile.master = true;
			this.workers.get(workerIndex).files.put(newFile.fileId, newFile);
			workerIndex = (workerIndex + 1) % numWorkers;
		}
		for (int i = 0; i < numWorkers; i++){
			this.workers.get(i).start();
		}
	}

	@Override
	protected void handleMessage(Message message) {
		super.handleMessage(message);
	}

	@Override
	protected void handleRequest(Request request) {
		super.handleRequest(request);
		
	}

	protected WorkerServer getReplicationTarget(Request r, String source){
		WorkerServer target = null;
		float load = Float.MAX_VALUE;
		int storage = 0;
		for (WorkerServer w : workers){
			if(w.getUniqueIdentifier().equals(source)){
				continue;
			}
			if(w.files.isEmpty()){
				return w;
			}
			if (!w.hasFile(r.fileId) && w.remainingSpace() >= files.get(r.fileId).size && w.getLoad() <= load && w.remainingSpace() > storage){
				target = w;
				load = w.getLoad();
				storage = w.remainingSpace();
			}
		}
		if(load < super.MAX_WORKER_LOAD){
			return target;	
		}else{
			return null;
		}
	}
	
	protected boolean checkForNewServer(){
		boolean startNew = true;
		for(WorkerServer w : workers){
			if(w.getLoad() < simulationParameters.getHighLoadThreshold()){
				startNew = false;
				break;
			}
		}
		
		if(!startNew){
			return false;
		}else{
			//Start up new server
			System.out.println("NEW SERVER: front end: {" + this.id + "}");
			WorkerServer newServer = new WorkerServer(workerId++, simulationParameters, this, monitor);
			this.workers.add(newServer);
			newServer.start();
			
			int numFilesToCopy = (int)Math.floor((double)simulationParameters.getStorageSize() / (double)simulationParameters.getBlocksPerFile());
			ArrayList<File> hottest = new ArrayList<File>();
			//Send it some stuff
			for(File f : files.values()){
				if(!hasFileBlock(f.fileId, 0)){
					continue;
				}
				for(int i = 0; i < hottest.size(); i++){
					if(f.getHotness(this.time, simulationParameters.getHotnessInterval()) < hottest.get(i).getHotness(this.time, simulationParameters.getHotnessInterval())){
						hottest.add(i, f);
					}
					if(hottest.size() > numFilesToCopy){
						hottest.remove(hottest.size()-1);
					}
				}
			}
			
			for(File f : hottest){
				for(WorkerServer w : workers){
					if(w.hasFileBlock(f.fileId, 0)){
						w.sendReplication(f, newServer);
						continue;
					}
				}
			}
			return true;
		}
		
	}
	
	public boolean checkForDeleteServer(){
		Queue<WorkerServer> workerQ = new LinkedList<WorkerServer>();
		for(WorkerServer w : workers){
			workerQ.offer(w);
		}
		
		WorkerServer w1;
		WorkerServer w2;
		while(!workerQ.isEmpty()){
			w1 = workerQ.poll();
			int stop = workerQ.size();
			for(int j = 0; j < stop; j++){
				w2 = workerQ.poll();
				boolean canShutDown1 = true;
				boolean canShutDown2 = true;
				if(!w1.equals(w2)){
					if(!canMerge(w1) || w1.getLoad() > simulationParameters.getLowLoadThreshold() || w2.getLoad() > simulationParameters.getLowLoadThreshold()){
						canShutDown1 = false;
					}else{
						for(Request r : w1.requests){
							if(!w2.hasFileBlock(r.fileId, r.currentBlockId)){
								canShutDown1 = false;
							}
						}
					}
					
					if(!canShutDown1){
						if(!canMerge(w2) || w2.getLoad() > simulationParameters.getLowLoadThreshold() || w1.getLoad()>simulationParameters.getLowLoadThreshold()){
							canShutDown2 = false;
						}else{
							for(Request r : w2.requests){
								if(!w1.hasFileBlock(r.fileId, r.currentBlockId)){
									canShutDown2 = false;
								}
							}
						}
					}
					
					if(!canShutDown1 && !canShutDown2){
						workerQ.offer(w2);
						continue;
					}
					
					WorkerServer shutdown = (canShutDown1) ? w1 : w2;
					WorkerServer other = (canShutDown1) ? w2 : w1;
					shutdown.isShuttingDown = true;
					
					while(!shutdown.requests.isEmpty()){
						other.addRequest(shutdown.requests.poll());
					}
					
					for(Integer fileId: shutdown.files.keySet()){
						delete.deleteUpdate(fileId);
					}
					
					deletions++;
					System.out.println("SHUT DOWN WORKER: " + w2.getUniqueIdentifier());
					workers.remove(shutdown);
					shutdown.die();
					deadWorkers.put(shutdown.id, shutdown);
				}
			}
		}
		synchronized(this){
			this.notifyAll();
		}
		return true;
	}
	
	protected boolean canMerge(WorkerServer w){
		for(File f : w.files.values()){
			if(f.master){
				return false;
			}
		}
		if(w.isReplicating()){
			return false;
		}
		return true;
	}
	
	@Override
	protected void runAlgorithm() {
		if(!checkForNewServer()){
			checkForDeleteServer();
		}
		super.runAlgorithm();
	}
	
	@Override
	public boolean isDone() {
		return super.isDone();
	}

}
